/* QtLua -- Lua bindings for Qt
   Copyright (C) 2011, Jarek Pelczar

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 3 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General
   Public License along with this library; if not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301 USA

*/

#include "qluascriptengine.h"
#include "qluascriptengine_p.h"
#include "qluaengineglobals_p.h"
#include "qluascriptvalue_p.h"
#include "qluascriptcontext_p.h"
#include "qluaglobal.h"
#include "qluametaobjectwrapper_p.h"
#include "qluaqtnamespace_p.h"
#include "qluastandardwrappers_p.h"
#include "qluaexposedproperty_p.h"
#include "qluascriptprogram.h"
#include "qluascriptengineagent_p.h"
#include "qluascriptcontextinfo_p.h"
#include "qluascriptcontext_p.h"
#include "qluaexception.h"
#include "qluascriptplugin.h"
#include "qluaqobjectwrapper_p.h"

#include <QPluginLoader>
#include <QFile>

extern "C" {
#include "ldo.h"
#include "lfunc.h"
#include "lmem.h"
#include "lobject.h"
#include "lopcodes.h"
#include "lstring.h"
#include "lundump.h"
}

namespace QLuaInternal {

QLuaException qt_lua_MakeException(QLuaScriptEngine * engine,
                                   qint64 scriptId,
                                   QLuaScriptValue contents);

}

QLuaScriptEnginePrivate::QLuaScriptEnginePrivate()
{
    m_debugger = NULL;
    m_lastScriptId = 0;
    m_gcForced = false;
}

QLuaScriptEnginePrivate::~QLuaScriptEnginePrivate()
{
    delete m_debugger;
    m_debugger = NULL;

    QLuaInternal::QLinkedListNode * node;

    // Clear object references before GC
    while((node = m_values.m_next) != &m_values)
    {
        QLuaInternal::QLinkedListElement<QLuaScriptValueData> * data = static_cast<QLuaInternal::QLinkedListElement<QLuaScriptValueData> *>(node);
        data->m_ptr->clear();
        node->removeMe();
        data->m_ptr->m_engine = NULL;
    }

    lua_close(L);

#ifdef QT_DEBUG
    qDebug() << "[QtLua] All pointers released";
#endif

    QLuaEngineGlobals::unregisterEngine(L);
    L = NULL;

#ifdef QT_DEBUG
    if(!m_allocedPtrs.isEmpty()) {
        qWarning() << "There are unrealeased pointers:";
        qWarning() << m_allocedPtrs;
    }
#endif
}

void QLuaScriptEnginePrivate::init()
{
    L = lua_newstate(l_alloc, this);
    if(!L)
    {
        qWarning("Failed to create lua engine state");
        return;
    }
    lua_atpanic(L, l_panic);
    lua_gc(L, LUA_GCSTOP, 0);
    luaL_openlibs(L);
    lua_gc(L, LUA_GCRESTART, 0);
#ifdef QT_DEBUG
    qDebug() << "[QtLua] Created lua state at" << L;
#endif

    QLuaScriptContextPrivate * root_ctx = new QLuaScriptContextPrivate;

    root_ctx->m_engine = q_ptr;
    root_ctx->m_parent = 0;
    root_ctx->m_thisObject = q_ptr->getGlobal("_G");

    m_rootContext = m_currentContext = new QLuaScriptContext(root_ctx);

    q_ptr->setGlobal("QtLua", q_ptr->newTable());

    QLuaInternal::QLuaQObjectWrapper::registerModule(q_ptr);
    QLuaInternal::QLuaMetaObjectWrapper::registerModule(q_ptr);
    QLuaInternal::QLuaQtNamespace::registerModule(q_ptr);
    QLuaInternal::QLuaStandardWrappers::registerModule(q_ptr);

    m_weakRefsTable = q_ptr->newWeakTable();

    lua_sethook(L, &QLuaScriptEnginePrivate::context_hook, LUA_MASKCALL|LUA_MASKRET, 0);
}

void * QLuaScriptEnginePrivate::l_alloc (void *ud, void *ptr, size_t osize, size_t nsize)
{
    Q_UNUSED(ud);
    Q_UNUSED(osize);

#ifdef QT_DEBUG
    QLuaScriptEnginePrivate * self = reinterpret_cast<QLuaScriptEnginePrivate *>(ud);
#endif

    if(nsize == 0)
    {
#ifdef QT_DEBUG
        if(!ptr)
            return NULL;
        if(self->m_allocedPtrs.remove(ptr) != 1)
        {
            qWarning("[QtLua] Lua engine tried to free pointer %p not allocated by the engine", ptr);
            return NULL;
        }
#endif
        qFree(ptr);
        return NULL;
    } else if(!ptr) {
        void * ptr = qMalloc(nsize);
#ifdef QT_DEBUG
        if(ptr)
            self->m_allocedPtrs.insert(ptr, nsize);
#endif
        return ptr;
    } else {
#ifdef QT_DEBUG
        if(self->m_allocedPtrs.remove(ptr) != 1)
        {
            qWarning("[QtLua] Tried to reallocate pointer %p not allocated by the engine", ptr);
            return NULL;
        }
#endif

        void *newPtr = qRealloc(ptr, nsize);

#ifdef QT_DEBUG
        if(newPtr)
            self->m_allocedPtrs.insert(newPtr, nsize);
#endif

        return newPtr;
    }
}

int QLuaScriptEnginePrivate::l_panic (lua_State *L)
{
    qFatal("[QtLua] Unprotected error in call to Lua API (%s)\n", lua_tostring(L, -1));
    return 0;
}

int QLuaScriptEnginePrivate::invoke_traceback(lua_State * L)
{
    if(!lua_isuserdata(L, lua_upvalueindex(1)))
    {
        qWarning("invoke_traceback: upvalue is not userdata");
        return 1;
    }

    QLuaScriptEnginePrivate * self = (QLuaScriptEnginePrivate *)lua_touserdata(L, lua_upvalueindex(1));
    QLuaScriptContext * context = self->m_currentContext;

    QLuaScriptContextInfo info(context);

    QLuaException ex(QLuaInternal::qt_lua_MakeException(self->q_ptr,
                                                        info.scriptId(),
                                                        QLuaScriptValue::newFromStack(self->q_ptr, 1)));

    self->m_lastException = ex;

    // First try to dispatch by lua debugger
    lua_getfield(L, LUA_GLOBALSINDEX, "debug");
    if(!lua_istable(L, -1))
    {
        lua_pop(L, 1);
        return 1;
    }

    lua_getfield(L, -1, "traceback");
    if(!lua_isfunction(L, -1))
    {
        lua_pop(L, 2);
        return 1;
    }

    lua_pushvalue(L, 1);
    lua_pushinteger(L, 2);
    lua_call(L, 2, 1);

#ifdef QT_DEBUG
    qDebug() << "[QtLua]" << lua_tostring(L, -1);
#endif

    return 1;
}

int QLuaScriptEnginePrivate::do_call(int narg, int nclean)
{
    int status;
    m_lastException = QLuaException();
    int base = lua_gettop(L) - narg;
    lua_pushlightuserdata(L, (void *)this);
    lua_pushcclosure(L, invoke_traceback, 1);
    lua_insert(L, base);
    status = lua_pcall(L, narg, nclean ? 0 : LUA_MULTRET, base);
    lua_remove(L, base);
    if(status != 0)
        lua_gc(L, LUA_GCCOLLECT, 0);
    return status;
}

void QLuaScriptEnginePrivate::_q_objectDestroyed(QObject * o)
{
    m_wrappedObjects.remove(o);
}

bool QLuaScriptEnginePrivate::convertScriptValueToPropertyType(bool flagsOrEnum, int userType, const QLuaScriptValue& value, QVariant& result)
{
    if(flagsOrEnum)
    {
        if(!value.isNumber())
            return false;
        result = value.toNumber();
        return true;
    }

    switch(userType)
    {
    case QVariant::Invalid: return false;
    case QVariant::Bool:
        if(!value.isBool())
            return false;
        result = value.toBool(); return true;
    case QVariant::Int:
        if(!value.isNumber())
            return false;
        result = (int)value.toInteger(); return true;
    case QVariant::UInt:
        if(!value.isNumber())
            return false;
        result = (uint)value.toInteger(); return true;
    case QVariant::LongLong:
        if(!value.isNumber())
            return false;
        result = (qint64)value.toInteger(); return true;
    case QVariant::ULongLong:
        if(!value.isNumber())
            return false;
        result = (quint64)value.toInteger(); return true;
    case QVariant::Double:
        if(!value.isNumber())
            return false;
        result = (double)value.toNumber(); return true;
    case QVariant::Char:
        {
        if(!value.isString())
            return false;
        QString str = value.toUnicode();
        if(str.length() != 1)
            return false;
        result = QChar(value.toUnicode()[0]);
        return true;
        }
    case QVariant::String:
        if(!value.isString() && !value.isNumber() && !value.isBool())
            return false;
        result = value.toUnicode();
        return true;
    case QVariant::Map:        
        if(!value.isTable())
            return false;
        result = value.toMap();
        return true;
    case QVariant::List:
        if(!value.isTable())
            return false;
        result = value.toList();
        return true;
    case QVariant::StringList:
        if(!value.isTable())
            return false;
        result = value.toStringList();
        return true;
    case QVariant::Hash:
        if(!value.isTable())
            return false;
        result = value.toHash();
        return true;
    case QVariant::ByteArray:
        if(!value.isString())
            return false;
        result = value.toString();
        return true;
    case QVariant::BitArray:
        if(value.isString()) {
            QBitArray ba;
            QByteArray b_arr(value.toString());
            ba.resize(b_arr.length());
            for(int i = 0 ; i < b_arr.length() ; ++i)
            {
                ba.setBit(i, b_arr[i] == '1');
            }
            result = qVariantFromValue(ba);
            return true;
        }
        if(value.isTable()) {
            QVariantList list(value.toList());
            QBitArray ba;
            ba.resize(list.length());
            for(int i = 0 ; i < list.length() ; ++i)
            {
                ba.setBit(i, list.at(i).toBool());
            }
            result = qVariantFromValue(ba);
            return true;
        }
        return false;
    case QVariant::Url:
        if(!value.isString())
            return false;
        result = QUrl(value.toUnicode());
        return true;
    default:
        break;
    }

    int user_type = userType;

    switch(user_type)
    {
    case QMetaType::Long:
        {
        if(!value.isNumber())
            return false;
        long vv = value.toInteger();
        result = QVariant(user_type, (const void *)&vv);
        return true;
        }
    case QMetaType::Short:
        {
        if(!value.isNumber())
            return false;
        short vv = value.toInteger();
        result = QVariant(user_type, (const void *)&vv);
        return true;
        }
    case QMetaType::Char:
        {
        if(!value.isNumber())
            return false;
        char vv = value.toInteger();
        result = QVariant(user_type, (const void *)&vv);
        return true;
        }
    case QMetaType::ULong:
        {
        if(!value.isNumber())
            return false;
        unsigned long vv = value.toInteger();
        result = QVariant(user_type, (const void *)&vv);
        return true;
        }
    case QMetaType::UShort:
        {
        if(!value.isNumber())
            return false;
        unsigned short vv = value.toInteger();
        result = QVariant(user_type, (const void *)&vv);
        return true;
        }
    case QMetaType::UChar:
        {
        if(!value.isNumber())
            return false;
        unsigned char vv = value.toInteger();
        result = QVariant(user_type, (const void *)&vv);
        return true;
        }
    case QMetaType::Float:
        {
        if(!value.isNumber())
            return false;
        char vv = value.toInteger();
        result = QVariant(user_type, (const void *)&vv);
        return true;
        }
    case QMetaType::QObjectStar: {
        if(!value.isQObject())
            return false;
        QObject * oo = value.toQObject();
        if(!oo)
            return false;
        result = QVariant(user_type, (const QObject **)&oo);
        return true;
        }
    case QMetaType::QWidgetStar: {
        if(!value.isQObject())
            return false;
        QObject * oo = value.toQObject();
        if(!oo || !oo->isWidgetType())
            return false;
        result = QVariant(user_type, (const QObject **)&oo);
        return true;
        }
    case QMetaType::QVariant:
        return false;
    case QMetaType::VoidStar:
        if(value.isLightUserData())
        {
            void * ptr = value.toLightUserData();
            result = qVariantFromValue(ptr);
            return true;
        }
        if(value.isUserData())
        {
            void * ptr = value.toUserData();
            result = qVariantFromValue(ptr);
            return true;
        }
        return false;
    default:
        break;
    }

    if(m_metaRegistry.contains(user_type))
    {
        QLuaInternal::QLuaMetaTypeRegistryEntry * entry = m_metaRegistry[user_type];
        if(entry->m_fromScriptFunc)
        {
            QVariant vv(user_type, (const void *)NULL);
            if(!vv.isValid())
                return false;
            entry->m_fromScriptFunc(value, vv.data());
            result = vv;
            return true;
        }
    }

    // QLuaScriptValue inside variant
    if(user_type == qMetaTypeId<QLuaScriptValue>())
    {
        result = qVariantFromValue<QLuaScriptValue>(value);
        return true;
    }

    return false;
}

bool QLuaScriptEnginePrivate::scriptConnect(QObject * sender, const char * signal,
                   QLuaScriptValue receiver, QLuaScriptValue function,
                   Qt::ConnectionType type)
{
    Q_ASSERT(sender);
    Q_ASSERT(signal);
    const QMetaObject *meta = sender->metaObject();
    int index = meta->indexOfSignal(QMetaObject::normalizedSignature(signal+1));
    if (index == -1)
        return false;

    return scriptConnect(sender, index, receiver, function, QLuaScriptValue(), type);
}

bool QLuaScriptEnginePrivate::scriptDisconnect(QObject * sender, const char * signal,
                      QLuaScriptValue receiver, QLuaScriptValue function)
{
    Q_ASSERT(sender);
    Q_ASSERT(signal);
    const QMetaObject *meta = sender->metaObject();
    int index = meta->indexOfSignal(QMetaObject::normalizedSignature(signal+1));
    if (index == -1)
        return false;

    return scriptDisconnect(sender, index, receiver, function);
}


bool QLuaScriptEnginePrivate::scriptConnect(QObject * sender, int signalIndex,
                   QLuaScriptValue receiver, QLuaScriptValue function,
                   QLuaScriptValue senderWrapper,
                   Qt::ConnectionType type)
{
    QLuaScriptValue data(q_ptr->newQObject(sender));

    return false;
}

bool QLuaScriptEnginePrivate::scriptDisconnect(QObject * sender, int signalIndex,
                      QLuaScriptValue receiver, QLuaScriptValue function)
{
    QLuaScriptValue data(q_ptr->newQObject(sender));

    return false;
}

bool QLuaScriptEnginePrivate::scriptConnect(QLuaScriptValue signal, QLuaScriptValue receiver,
                   QLuaScriptValue function, Qt::ConnectionType type)
{
    return false;
}

bool QLuaScriptEnginePrivate::scriptDisconnect(QLuaScriptValue signal, QLuaScriptValue receiver,
                   QLuaScriptValue function)
{
    return false;
}

void QLuaScriptEnginePrivate::loadPlugins()
{
    QObjectList static_plugins = QPluginLoader::staticInstances();
    for(int i = 0 ; i < static_plugins.length() ; ++i)
    {
        QObject * o = static_plugins.at(i);
        if(QLuaScriptPlugin * plugin = qobject_cast<QLuaScriptPlugin *>(o)) {
            plugin->registerPlugin(q_ptr);
        }
    }

    QStringList paths(QLuaScriptEngine::pluginImportPaths());
    for(int i = 0 ; i < paths.count() ; ++i)
    {
        QDirIterator iter(paths.at(i), QDirIterator::Subdirectories);
        while(iter.hasNext()) {
            iter.next();
            if(QLibrary::isLibrary(iter.path()))
            {
                QPluginLoader loader(iter.path());
                if(loader.load()) {
                    QObject * o = loader.instance();
                    if(QLuaScriptPlugin * plugin = qobject_cast<QLuaScriptPlugin *>(o)) {
                        plugin->registerPlugin(q_ptr);
                    } else {
                        loader.unload();
                    }
                }
            }
        }
    }
}

QLuaScriptEngine::QLuaScriptEngine(QObject *parent) :
    QObject(parent)
{
    d_ptr = new QLuaScriptEnginePrivate();
    d_ptr->q_ptr = this;
    d_ptr->init();
    QLuaEngineGlobals::registerEngine(d_ptr->L, this, d_ptr);
    d_ptr->loadPlugins();
}

QLuaScriptEngine::~QLuaScriptEngine()
{
    d_ptr->m_dtorPending = true;
    delete d_ptr;
}

QLuaScriptValue QLuaScriptEngine::newTable()
{
    lua_State * L = d_ptr->L;
    if(!L)
        return QLuaScriptValue();
    lua_newtable(L);
    QLuaScriptValue rv(QLuaScriptValue::newFromStack(this, -1));
    lua_pop(L, 1);
    return rv;
}

QLuaScriptValue QLuaScriptEngine::newWeakTable()
{
    lua_State * L = d_ptr->L;
    if(!L)
        return QLuaScriptValue();
    lua_newtable(L);
    lua_newtable(L);
    lua_pushliteral(L, "v");
    lua_setfield(L, -2, "__mode");
    lua_setmetatable(L, -2);
    QLuaScriptValue rv(QLuaScriptValue::newFromStack(this, -1));
    lua_pop(L, 1);
    return rv;
}

QLuaScriptValue QLuaScriptEngine::newMetaObject(const QMetaObject * mo)
{
    return QLuaInternal::QLuaMetaObjectWrapper::create(this, mo);
}

QLuaScriptValue QLuaScriptEngine::newQObject(QObject * object, ValueOwnership ownership)
{
    if(!object)
        return QLuaScriptValue();

    if(d_ptr->m_wrappedObjects.contains(object))
    {
        QByteArray key(QByteArray::number((uint)object, 16));
        return d_ptr->m_weakRefsTable.property(key);
    }

    QLuaScriptValue rv(QLuaInternal::QLuaQObjectWrapper::newQObject(this, object, ownership));

    if(!rv.isNil())
    {
        d_ptr->m_wrappedObjects.insert(object);
        QByteArray key(QByteArray::number((uint)object, 16));
        d_ptr->m_weakRefsTable.setProperty(key, rv);
    }

    QLuaEngineGlobals::registerQObject(object, this);

    return rv;
}

struct CompileAgrs {
    QByteArray m_code;
    QByteArray m_fileName;
    bool m_strip;
    QByteArray m_compiled;
};

#define toproto(L,i) (clvalue(L->top+(i))->l.p)

static const Proto* combine(lua_State* L, int n, const char * programName)
{
    if (n==1)
        return toproto(L,-1);
    else
    {
        int i,pc;
        Proto* f=luaF_newproto(L);
        setptvalue2s(L,L->top,f); incr_top(L);
        f->source = luaS_newliteral(L, "=(luac)");
        f->maxstacksize=1;
        pc=2*n+1;
        f->code=luaM_newvector(L,pc,Instruction);
        f->sizecode=pc;
        f->p=luaM_newvector(L,n,Proto*);
        f->sizep=n;
        pc=0;
        for (i=0; i<n; i++)
        {
            f->p[i]=toproto(L,i-n-1);
            f->code[pc++]=CREATE_ABx(OP_CLOSURE,0,i);
            f->code[pc++]=CREATE_ABC(OP_CALL,0,1,1);
        }
        f->code[pc++]=CREATE_ABC(OP_RETURN,0,1,0);
        return f;
    }
}

static int code_writer(lua_State* L, const void* p, size_t size, void* u)
{
    QIODevice * ioDevice = (QIODevice *)u;

    ioDevice->write((const char *)p, size);
    return 0;
}

static int compile_chunk(lua_State * L)
{
    CompileAgrs * args = (CompileAgrs *)lua_touserdata(L, 1);

    if(luaL_loadbuffer(L,
                    args->m_code.constData(),
                    args->m_code.length(),
                    args->m_fileName.constData()) != 0)
    {
        return 1;
    }

    QBuffer resultBuffer(&args->m_compiled);
    resultBuffer.open(QIODevice::WriteOnly);

    const Proto * f = combine(L, 1, "=(luac)");

    lua_lock(L);
    luaU_dump(L, f, code_writer, &resultBuffer, args->m_strip ? 1 : 0);
    lua_unlock(L);

    resultBuffer.close();

    return 0;
}

QByteArray QLuaScriptEngine::compileCodeChunk(const QByteArray& code, const QByteArray& fileName, bool stripDebugInfo)
{
    lua_State * L = lua_open();
    if(!L) {
        qWarning("Lua - failed to create lua state");
        return QByteArray();
    }

    CompileAgrs args;
    args.m_code = code;
    args.m_fileName = fileName;
    args.m_strip = stripDebugInfo;

    if(lua_cpcall(L, compile_chunk, &args) != 0)
    {
        qWarning("[QtLua] Failed to compile code: %s", lua_tostring(L, -1));
        lua_pop(L, 1);
        lua_close(L);
        return QByteArray();
    }

    lua_close(L);

    return args.m_compiled;
}

QLuaScriptValue QLuaScriptEngine::getGlobal(const QByteArray& name) const
{
    if(!d_ptr->L)
        return QLuaScriptValue();
    lua_State * L = d_ptr->L;
    lua_getglobal(L, name.constData());
    QLuaScriptValue rv(QLuaScriptValue::newFromStack(const_cast<QLuaScriptEngine *>(this), -1));
    lua_pop(L, 1);
    return rv;
}

void QLuaScriptEngine::setGlobal(const QByteArray& key, const QLuaScriptValue& value)
{
    if(!d_ptr->L)
        return;
    lua_State * L = d_ptr->L;
    value.pushToStack(this);
    lua_setglobal(L, key.constData());
}

void QLuaScriptEngine::executeProgram(const QByteArray& code, const QByteArray& fileName)
{
    if(!d_ptr->L)
        return;
    lua_State * L = d_ptr->L;

    int cur_stack = lua_gettop(L);

    qint64 id = ++d_ptr->m_lastScriptId;

    QByteArray _fileName(fileName);

    if(!fileName.isEmpty()) {
        d_ptr->m_fileNameToProgramId.insert(fileName, id);
#ifdef QT_DEBUG
        qDebug() << "Register program" << fileName << "at" << id;
#endif
    } else {
        _fileName = "-lua-program-";
        _fileName.append(id);
    }

    QLuaScriptProgram program(code, _fileName);
    d_ptr->m_programs.insert(id, program);

    if(d_ptr->m_debugger && !code.startsWith(LUA_SIGNATURE)) {
        d_ptr->m_debugger->m_agent->scriptLoad(id, program.text(), program.fileName(), 0);
    }

    int status = luaL_loadbuffer(L, code.constData(),
                                 code.size(),
                                 _fileName.constData());

    if(status == 0)
    {
        status = d_ptr->do_call(0, 0);
    }


    if( status != 0 )
    {
#ifdef QT_DEBUG
        QLuaScriptValue error(QLuaScriptValue::newFromStack(this, -1));
#endif
        lua_pop(L, 1);
#ifdef QT_DEBUG
        qWarning() << "[QtLua] Execution failed -" << error.toVariant();
#endif
        return;
    }

    int ret_args = lua_gettop(L) - cur_stack;

    if(ret_args == 0)
        return;
    lua_pop(L, ret_args);
}

void QLuaScriptEngine::executeProgram(const QLuaScriptProgram& program)
{
    qint64 id = ++d_ptr->m_lastScriptId;

    QByteArray fileName;
    if(program.fileName().isEmpty()) {
        fileName = "-lua-program-";
        fileName.append(id);
    } else {
        fileName = program.fileName().toUtf8();
    }

    lua_State * L = d_ptr->L;
    int cur_stack = lua_gettop(L);
    int status;

    d_ptr->m_programs.insert(id, program);

    if(!fileName.isEmpty()) {
        d_ptr->m_fileNameToProgramId.insert(fileName, id);
    }

    if(program.type() == QLuaScriptProgram::ByteCode) {
        status = luaL_loadbuffer(L, program.byteCode().constData(),
                                 program.byteCode().size(),
                                 fileName.constData());
    } else {
        if(d_ptr->m_debugger) {
            d_ptr->m_debugger->m_agent->scriptLoad(id, program.text(), program.fileName(), 0);
        }

        QByteArray text(program.text().toUtf8());
        status = luaL_loadbuffer(L, text.constData(),
                                 text.size(),
                                 fileName.constData());
    }

    if(status == 0)
    {
        status = d_ptr->do_call(0, 0);
    }

    if( status != 0 )
    {
#ifdef QT_DEBUG
        QLuaScriptValue error(QLuaScriptValue::newFromStack(this, -1));
#endif
        lua_pop(L, 1);
#ifdef QT_DEBUG
        qWarning() << "[QtLua] Execution failed -" << error.toVariant();
#endif
        return;
    }

    int ret_args = lua_gettop(L) - cur_stack;

    if(ret_args == 0)
        return;
    lua_pop(L, ret_args);
}

void QLuaScriptEngine::executeFile(const QString& path)
{
    QFile file(path);
    if(file.open(QFile::ReadOnly)) {
        QByteArray contents = file.readAll();
        file.close();

        executeProgram(contents, path.toUtf8());
    } else {
        qWarning() << "[QtLua] Failed to load" << path << "(" << file.errorString() << ")";
    }
}

QLuaScriptValue QLuaScriptEngine::newFromVariant(const QVariant& value)
{
    switch(value.type())
    {
    case QVariant::Invalid: return QLuaScriptValue();
    case QVariant::Bool: return QLuaScriptValue(value.toBool());
    case QVariant::Int:
    case QVariant::UInt:
    case QVariant::LongLong:
    case QVariant::ULongLong:
    case QVariant::Double:
        return QLuaScriptValue(qreal(value.toDouble()));
    case QVariant::Char:
    case QVariant::String:
        return QLuaScriptValue(value.toString());
    case QVariant::Map:
        {
        QLuaScriptValue rv(newTable());
        QMapIterator<QString, QVariant> it(value.toMap());
        while(it.hasNext())
            {
            rv.setProperty(it.key().toUtf8(), newFromVariant(it.value()));
            }
        return rv;
        }
    case QVariant::List:
        {
        QLuaScriptValue rv(newTable());
        QListIterator<QVariant> it(value.toList());
        int index = 0;
        while(it.hasNext())
            {
            rv.setProperty(index++, newFromVariant(it.next()));
            }
        return rv;
        }
    case QVariant::Hash:
        {
        QLuaScriptValue rv(newTable());
        QHashIterator<QString, QVariant> it(value.toHash());
        while(it.hasNext())
            {
            rv.setProperty(it.key().toUtf8(), newFromVariant(it.value()));
            }
        return rv;
        }
    case QVariant::StringList:
        {
        QLuaScriptValue rv(newTable());
        QStringListIterator it(value.toStringList());
        int index = 0;
        while(it.hasNext())
            {
            rv.setProperty(index++, QLuaScriptValue(it.next()));
            }
        return rv;
        }
    case QVariant::ByteArray:
        return QLuaScriptValue(value.toByteArray());
    case QVariant::BitArray:
        {
        QLuaScriptValue rv(newTable());
        QBitArray ba(value.toBitArray());
        QLuaScriptValue int_0(int(0));
        QLuaScriptValue int_1(int(1));
        for(int i = 0 ; i < ba.size() ; ++i)
            {
            if(ba.testBit(i))
                rv.setProperty(i, int_1);
            else
                rv.setProperty(i, int_0);
            }
        return rv;
        }
    case QVariant::Url:
        return QLuaScriptValue(value.toString());
    default:
        break;
    }

    int user_type = value.userType();

    switch(user_type)
    {
    case QMetaType::Long:
    case QMetaType::Short:
    case QMetaType::Char:
    case QMetaType::ULong:
    case QMetaType::UShort:
    case QMetaType::UChar:
    case QMetaType::Float:
        return QLuaScriptValue(qreal(value.toDouble()));
    case QMetaType::QObjectStar:
        return newQObject(value.value<QObject *>());
    case QMetaType::QWidgetStar:
        return newQObject(value.value<QObject *>());
    case QMetaType::QVariant:
        return newFromVariant(value.value<QVariant>());
    case QMetaType::VoidStar:
        return newLightUserData(value.value<void *>());
    default:
        break;
    }

    if(d_ptr->m_metaRegistry.contains(user_type))
    {
        QLuaInternal::QLuaMetaTypeRegistryEntry * entry = d_ptr->m_metaRegistry[user_type];
        if(entry->m_toScriptFunc)
        {
            return entry->m_toScriptFunc(this, value.constData());
        }
    }

    // QLuaScriptValue inside variant
    if(user_type == qMetaTypeId<QLuaScriptValue>())
    {
        return qVariantValue<QLuaScriptValue>(value);
    }

    qWarning("[QtLua] Unable to find conversion from type %s to Lua type", value.typeName());
    return QLuaScriptValue();
}

static int dispatch_Lua_Method(lua_State * L)
{
    QLuaScriptEngine * engine = QLuaEngineGlobals::engineFromLua(L);
    Q_ASSERT_X(engine != NULL, "QLuaScriptEnginePrivate::dispatch_Lua_Method", "Called from incorrect context");
    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);
    Q_ASSERT(L == priv->L);

    QLuaScriptEngine::FunctionSignature signature = (QLuaScriptEngine::FunctionSignature)
            lua_touserdata(L, lua_upvalueindex(1));

    int arg_count = lua_gettop(L);
    if(arg_count < 1) {
        return luaL_error(L, "QtLua: Method must be called with object");
    }

    QLuaScriptValue selfObject(QLuaScriptValue::newFromStack(engine, 1));

    QVector<QLuaScriptValue> args(arg_count - 1);
    for(int i = 0 ; i < args.count() ; ++i)
        args[i] = QLuaScriptValue::newFromStack(engine, i + 2);

    QLuaInternal::QLuaScriptContextCleanup script_context_cleanup(
                priv,
                priv->m_currentContext,
                args,
                selfObject,
                selfObject.toQObject());        

    QLuaScriptValue return_value(signature(priv->m_currentContext, engine));

    if(!return_value.isValid())
        return 0;

    return_value.pushToStack(engine);

    return 1;
}

static int dispatch_Lua_Function(lua_State * L)
{
    QLuaScriptEngine * engine = QLuaEngineGlobals::engineFromLua(L);
    Q_ASSERT_X(engine != NULL, "QLuaScriptEnginePrivate::dispatch_Lua_Method", "Called from incorrect context");
    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);
    Q_ASSERT(L == priv->L);

    QLuaScriptEngine::FunctionSignature signature = (QLuaScriptEngine::FunctionSignature)
            lua_touserdata(L, lua_upvalueindex(1));

    int arg_count = lua_gettop(L);

    QLuaScriptValue selfObject(engine->getGlobal("_G"));

    QVector<QLuaScriptValue> args(arg_count);
    for(int i = 0 ; i < args.count() ; ++i)
        args[i] = QLuaScriptValue::newFromStack(engine, i + 1);

    QLuaInternal::QLuaScriptContextCleanup script_context_cleanup(
                priv,
                priv->m_currentContext,
                args,
                selfObject,
                selfObject.toQObject());

    QLuaScriptValue return_value(signature(priv->m_currentContext, engine));

    if(!return_value.isValid())
        return 0;

    return_value.pushToStack(engine);

    return 1;
}

QLuaScriptValue QLuaScriptEngine::newMethod(FunctionSignature signature)
{
    lua_State * L = d_ptr->L;

    lua_pushlightuserdata(L, (void *)signature);
    lua_pushcclosure(L, dispatch_Lua_Method, 1);

    QLuaScriptValue rv(QLuaScriptValue::newFromStack(this, -1));

    lua_pop(L, 1);

    return rv;
}

QLuaScriptValue QLuaScriptEngine::newFunction(FunctionSignature signature)
{
    lua_State * L = d_ptr->L;

    lua_pushlightuserdata(L, (void *)signature);
    lua_pushcclosure(L, dispatch_Lua_Function, 1);

    QLuaScriptValue rv(QLuaScriptValue::newFromStack(this, -1));

    lua_pop(L, 1);

    return rv;
}

QLuaScriptEngineAgent * QLuaScriptEngine::agent() const
{
    if(!d_ptr->m_debugger)
        return NULL;
    return d_ptr->m_debugger->m_agent;
}

void QLuaScriptEngine::setAgent(QLuaScriptEngineAgent * agent)
{
    lua_State * L = d_ptr->L;

    if(!agent) {
        if(!d_ptr->m_debugger)
            return;
        delete d_ptr->m_debugger;
        d_ptr->m_debugger = NULL;

        lua_sethook(L, &QLuaScriptEnginePrivate::context_hook, LUA_MASKCALL|LUA_MASKRET, 0);
    } else {
        QLuaScriptEngineAgentPrivate * priv = QLuaScriptEngineAgentPrivate::priv(agent);

        if(d_ptr->m_debugger) {
            delete d_ptr->m_debugger;
            d_ptr->m_debugger = NULL;
        }

        d_ptr->m_debugger = priv;

        lua_sethook(L, &QLuaScriptEnginePrivate::debug_hook, LUA_MASKCALL|LUA_MASKRET|LUA_MASKLINE, 0);
    }
}

void QLuaScriptEnginePrivate::debug_hook(lua_State *L, lua_Debug *ar)
{
    QLuaScriptEngine * engine = QLuaEngineGlobals::engineFromLua(L);
    Q_ASSERT_X(engine != NULL, "QLuaScriptEnginePrivate::debug_hook", "Called from incorrect context");
    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);
    Q_ASSERT(L == priv->L);
    QLuaScriptEngineAgent * agent = priv->m_debugger ? priv->m_debugger->m_agent : NULL;

    switch(ar->event)
    {
    case LUA_HOOKCALL:
        lua_getinfo(L, "nSlu", ar);
        priv->dispatchCallEnter(ar, true);
        if(agent) {
            QLuaScriptContextInfoPrivate * ctxInfo = QLuaScriptContextPrivate::priv(priv->m_currentContext)->m_contextInfo;
            if(ctxInfo) {
                agent->functionEntry(ctxInfo->m_scriptId);
            }
        }
        break;            
    case LUA_HOOKRET:        
        if(agent) {
            QLuaScriptContextInfoPrivate * ctxInfo = QLuaScriptContextPrivate::priv(priv->m_currentContext)->m_contextInfo;
            if(ctxInfo) {
                agent->functionExit(ctxInfo->m_scriptId, QLuaScriptValue());
            }
        }
        priv->dispatchCallExit(ar, true);
        break;
    case LUA_HOOKLINE:
        if(agent) {
            QLuaScriptContextInfoPrivate * ctxInfo = QLuaScriptContextPrivate::priv(priv->m_currentContext)->m_contextInfo;
            if(ctxInfo) {
                lua_getinfo(L, "l", ar);
                ctxInfo->m_currentLine = ar->currentline;
                agent->positionChange(ctxInfo->m_scriptId, ar->currentline, 0);
            }
        }
        break;
    default:
        break;
    }
}

void QLuaScriptEnginePrivate::context_hook(lua_State *L, lua_Debug *ar)
{
    QLuaScriptEngine * engine = QLuaEngineGlobals::engineFromLua(L);
    Q_ASSERT_X(engine != NULL, "QLuaScriptEnginePrivate::debug_hook", "Called from incorrect context");
    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);
    Q_ASSERT(L == priv->L);

    switch(ar->event)
    {
    case LUA_HOOKCALL:
        priv->dispatchCallEnter(ar, false);
        break;
    case LUA_HOOKRET:
        priv->dispatchCallExit(ar, false);
        break;
    default:
        break;
    }
}

void QLuaScriptEnginePrivate::dispatchCallEnter(lua_Debug * ar, bool use_agent)
{
    QLuaScriptContextPrivate * privContext = new QLuaScriptContextPrivate();
    privContext->m_engine = this->q_ptr;
    privContext->m_parent = this->m_currentContext;

    QLuaScriptContext * context = new QLuaScriptContext(privContext);
    m_currentContext = context;

    privContext->pushRecord();

    if(use_agent) {
        privContext->setupInfo(ar);
        this->m_debugger->m_agent->contextPush();
    }
}

void QLuaScriptEnginePrivate::dispatchCallExit(lua_Debug * ar, bool use_agent)
{
    if(use_agent) {
        this->m_debugger->m_agent->contextPop();
    }

    QLuaScriptContext * current = m_currentContext;
    m_currentContext = current->parentContext();

    QLuaScriptContextPrivate::priv(current)->popRecord();
    delete current;
}

QHash<qint64, QLuaScriptProgram> QLuaScriptEngine::loadedScripts() const
{
    return d_ptr->m_programs;
}

QLuaScriptContext * QLuaScriptEngine::currentContext() const
{
    return d_ptr->m_currentContext;
}

QLuaScriptContext * QLuaScriptEngine::rootContext() const
{
    return d_ptr->m_rootContext;
}

QLuaScriptValue QLuaScriptEngine::newUserData(int sizeOfObject)
{
    lua_State * L = d_ptr->L;
    if(!L)
        return QLuaScriptValue();
    void * ptr = lua_newuserdata(L, sizeOfObject);
    if(!ptr) {
        qWarning() << "[QtLua] Failed to allocate pointer of size" << sizeOfObject;
        return QLuaScriptValue();
    }
    QLuaScriptValue rv(QLuaScriptValue::newFromStack(this, -1));
    lua_pop(L, 1);
    return rv;
}

void QLuaScriptEngine::registerUserData(void * ptr, int metaType)
{
    if(d_ptr->m_ptrMetaTypes.contains(ptr)) {
        qFatal("Pointer %p (MT=%d) already registered in engine with MetaType=%d", ptr, metaType,
               d_ptr->m_ptrMetaTypes.value(ptr));
    }

    d_ptr->m_ptrMetaTypes.insert(ptr, metaType);
}

void QLuaScriptEngine::unregisterUserData(void * ptr, int metaType)
{
    if(!d_ptr->m_ptrMetaTypes.contains(ptr)) {
        qFatal("Pointer %p (MT=%d) not  registered in engine", ptr, metaType);
    }

    if(d_ptr->m_ptrMetaTypes.take(ptr) != metaType) {
        qFatal("Pointer %p registered with another metatype %d", ptr, metaType);
    }
}

QLuaScriptValue QLuaScriptEngine::newLightUserData(void * ptr)
{
    lua_State * L = d_ptr->L;
    if(!L)
        return QLuaScriptValue();
    lua_pushlightuserdata(L, ptr);
    QLuaScriptValue rv(QLuaScriptValue::newFromStack(this, -1));
    lua_pop(L, 1);
    return rv;
}

static int dispatch_udata_gc(lua_State * L)
{
    QLuaScriptEngine * engine = QLuaEngineGlobals::engineFromLua(L);
    Q_ASSERT_X(engine != NULL, "QLuaScriptEnginePrivate::dispatch_Lua_Method", "Called from incorrect context");
    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);
    Q_ASSERT(L == priv->L);

    typedef void (* dtor_helper_t)(void *);

    dtor_helper_t dtor = (dtor_helper_t)lua_touserdata(L, lua_upvalueindex(1));

    dtor(lua_touserdata(L, 1));
    priv->m_ptrMetaTypes.remove(lua_touserdata(L, 1));

#ifdef QT_DEBUG
    qDebug() << "[QtLua] Destroy userdata at ptr" << lua_touserdata(L, 1);
#endif

    return 0;
}

static int dispatch_udata_index(lua_State * L)
{
    QLuaScriptEngine * engine = QLuaEngineGlobals::engineFromLua(L);
    Q_ASSERT_X(engine != NULL, "QLuaScriptEnginePrivate::dispatch_Lua_Method", "Called from incorrect context");
    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);
    Q_ASSERT(L == priv->L);

    QLuaScriptValue wrapper_object(QLuaScriptValue::newFromStack(engine, lua_upvalueindex(1)));
    if(QLuaInternal::QLuaQObjectWrapper * qwrapper = qtlua_scriptvalue_cast<QLuaInternal::QLuaQObjectWrapper>(wrapper_object))
    {
        QLuaScriptValue result(qwrapper->handle_index(wrapper_object,
                                                      QLuaScriptValue::newFromStack(engine, 1),
                                                      QLuaScriptValue::newFromStack(engine, 2)));
        if(result.isValid()) {
            result.pushToStack(engine);
            return 1;
        }

        return 0;
    }


    lua_gettable(L, lua_upvalueindex(1));
    return 1;
}

static int dispatch_udata_newindex(lua_State * L)
{
    QLuaScriptEngine * engine = QLuaEngineGlobals::engineFromLua(L);
    Q_ASSERT_X(engine != NULL, "QLuaScriptEnginePrivate::dispatch_Lua_Method", "Called from incorrect context");
    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);
    Q_ASSERT(L == priv->L);

    QLuaScriptValue wrapper_object(QLuaScriptValue::newFromStack(engine, lua_upvalueindex(1)));
    if(QLuaInternal::QLuaQObjectWrapper * qwrapper = qtlua_scriptvalue_cast<QLuaInternal::QLuaQObjectWrapper>(wrapper_object))
    {
        qwrapper->handle_newindex(wrapper_object,
                                  QLuaScriptValue::newFromStack(engine, 1),
                                  QLuaScriptValue::newFromStack(engine, 2),
                                  QLuaScriptValue::newFromStack(engine, 3));
        return 0;
    }

    lua_settable(L, lua_upvalueindex(1));
    return 1;
}

QLuaScriptValue QLuaScriptEngine::newUserDataWithGc(int metaTypeId,
                                                    int sizeOfT,
                                                    void (* ctor_helper)(void *),
                                                    void (* dtor_helper)(void *),
                                                    const QLuaScriptValue& proto)
{
    lua_State * L = d_ptr->L;
    if(!L)
        return QLuaScriptValue();

    void * ptr = lua_newuserdata(L, sizeOfT);

    ctor_helper(ptr);
    d_ptr->m_ptrMetaTypes.insert(ptr, metaTypeId);

    lua_newtable(L);

    lua_pushlightuserdata(L, (void *)dtor_helper);
    lua_pushcclosure(L, dispatch_udata_gc, 1);
    lua_setfield(L, -2, "__gc");

    if(proto.isValid())
    {
        proto.pushToStack(this);

        lua_pushvalue(L, -1);
        lua_pushcclosure(L, dispatch_udata_index, 1);
        lua_setfield(L, -3, "__index");

        lua_pushvalue(L, -1);
        lua_pushcclosure(L, dispatch_udata_newindex, 1);
        lua_setfield(L, -3, "__newindex");

        lua_pop(L, 1);
    }

    lua_setmetatable(L, -2);

    QLuaScriptValue rv(QLuaScriptValue::newFromStack(this, -1));
    lua_pop(L, 1);

#ifdef QT_DEBUG
    qDebug() << "[QtLua] Userdata allocated at" << ptr << ", metatype =" << metaTypeId <<
                ",object size =" << sizeOfT << ",class name =" << QMetaType::typeName(metaTypeId);
#endif

    return rv;
}

#include "wrap_convertableobject_p.h"

namespace QLuaInternal {

void registerLuaType(QLuaScriptEngine * engine, QLuaMetaTypeRegistryEntry * entry)
{
    Q_ASSERT_X(engine != NULL, "registerLuaType", "Engine must not be NULL");
    QLuaScriptEnginePrivate * priv = QLuaScriptEnginePrivate::priv(engine);
    if(priv->m_metaRegistry.contains(entry->m_typeId))
        return;
#ifdef QT_DEBUG
    qDebug() << "[QtLua] Register type" << QMetaType::typeName(entry->m_typeId) << "with meta id" << entry->m_typeId;
#endif
    priv->m_metaRegistry.insert(entry->m_typeId, entry);
}

}

void QLuaScriptEngine::collectGarbage()
{
    if(d_ptr->m_gcForced)
        return;
    d_ptr->m_gcForced = true;
    lua_gc(d_ptr->L, LUA_GCCOLLECT, 0);
    d_ptr->m_gcForced = false;
}

// Exception interface

bool QLuaScriptEngine::hasUncaughtException() const
{
    return !d_ptr->m_lastException.isNull();
}

QLuaException QLuaScriptEngine::uncaughtException() const
{
    return d_ptr->m_lastException;
}

void QLuaScriptEngine::clearException()
{
    d_ptr->m_lastException = QLuaException();
}

QLuaException QLuaScriptEngine::newException(const QString& what)
{
    return QLuaInternal::qt_lua_MakeException(this, -1, QLuaScriptValue(what));
}

QLuaException QLuaScriptEngine::newException(const QLuaScriptValue& what)
{
    return QLuaInternal::qt_lua_MakeException(this, -1, what);
}

void QLuaScriptEngine::throwException(const QLuaException& ex)
{
    {
        QByteArray exceptionString = ex.what().toString();
        QLuaScriptContextInfo info(d_ptr->m_currentContext);
        d_ptr->m_lastException = QLuaInternal::qt_lua_MakeException(this, info.scriptId(), ex.what());
        lua_pushlstring(d_ptr->L, exceptionString.constData(), exceptionString.length());
    }
    lua_error(d_ptr->L);
}

QStringList QLuaScriptEngine::pluginImportPaths()
{
    QLuaEngineGlobals * globals = QLuaEngineGlobals::get();
    QMutexLocker locker(&globals->m_lock);
    return globals->m_pluginImportPaths;
}

void QLuaScriptEngine::setPluginImportPaths(const QStringList& paths)
{
    QLuaEngineGlobals * globals = QLuaEngineGlobals::get();
    QMutexLocker locker(&globals->m_lock);
    globals->m_pluginImportPaths = paths;
}

void QLuaScriptEngine::addPluginImportPath(const QString& path)
{
    QLuaEngineGlobals * globals = QLuaEngineGlobals::get();
    QMutexLocker locker(&globals->m_lock);
    globals->m_pluginImportPaths.append(path);
}

#include "moc_qluascriptengine.cpp"
